// 2700. 两个对象之间的差异
// 提示
// 中等
// 2
// 请你编写一个函数，它接收两个深度嵌套的对象或数组 obj1 和 obj2 ，并返回一个新对象表示它们之间差异。
//
// 该函数应该比较这两个对象的属性，并识别任何变化。返回的对象应仅包含从 obj1 到 obj2 的值不同的键。对于每个变化的键，值应表示为一个数组 [obj1 value, obj2 value] 。不存在于一个对象中但存在于另一个对象中的键不应包含在返回的对象中。在比较两个数组时，数组的索引被视为它们的键。最终结果应是一个深度嵌套的对象，其中每个叶子的值都是一个差异数组。
//
// 你可以假设这两个对象都是 JSON.parse 的输出结果。
//
//
//
// 示例 1：
//
// 输入：
// obj1 = {}
// obj2 = {
//   "a": 1,
//   "b": 2
// }
// 输出：{}
// 解释：obj1没有进行任何修改。obj2中出现了新的键 "a" 和 "b" ，但添加或删除的键应该被忽略。
// 示例 2：
//
// 输入：
// obj1 = {
//   "a": 1,
//   "v": 3,
//   "x": [],
//   "z": {
//     "a": null
//   }
// }
// obj2 = {
//   "a": 2,
//   "v": 4,
//   "x": [],
//   "z": {
//     "a": 2
//   }
// }
// 输出：
// {
//   "a": [1, 2],
//   "v": [3, 4],
//   "z": {
//     "a": [null, 2]
//   }
// }
// 解释：键 "a"、"v" 和 "z" 都有变化。"a" 从 1 变为 2，"v" 从 3 变为 4 ，"z" 的子对象 "a" 从 null 变为 2。
// 示例 3：
//
// 输入：
// obj1 = {
//   "a": 5,
//   "v": 6,
//   "z": [1, 2, 4, [2, 5, 7]]
// }
// obj2 = {
//   "a": 5,
//   "v": 7,
//   "z": [1, 2, 3, [1]]
// }
// 输出：
// {
//   "v": [6, 7],
//   "z": {
//     "2": [4, 3],
//     "3": {
//       "0": [2, 1]
//     }
//   }
// }
// 解释：在 obj1 和 obj2 中，键 "v" 和 "z" 的值不同。"a" 被忽略，因为值没有变化。在键 "z" 中，有一个嵌套的数组。数组被视为对象，其中索引被视为键。数组发生了两处变化：z[2] 和 z[3][0]。z[0] 和 z[1] 没有变化，因此没有包含在结果中。z[3][1] 和 z[3][2] 被删除，因此也没有包含在结果中。
// 示例 4：
//
// 输入：
// obj1 = {
//   "a": {"b": 1},
// }
// obj2 = {
//   "a": [5],
// }
// 输出：
// {
//   "a": [{"b": 1}, [5]]
// }
// 解释：键 "a" 在两个对象中都存在。但由于两个相关值具有不同的类型，所以它们被放置在差异数组中。
// 示例 5：
//
// 输入：
// obj1 = {
//   "a": [1, 2, {}],
//   "b": false
// }
// obj2 = {
//   "b": false,
//   "a": [1, 2, {}]
// }
// 输出：
// {}
// 解释：除了键的顺序不同之外，两个对象是相同的，因此返回一个空对象。

/**
 * @param {object} obj1
 * @param {object} obj2
 * @return {object}
 */
function objDiff(obj1, obj2) {
  const superTypeof = (o) => {
    return Object.prototype.toString.call(o).slice(8, -1).toLowerCase()
  }
  const isPrim = (o) => {
    const t = superTypeof(o)
    return !['function', 'array', 'object', 'regex'].includes(t)
  }

  const res = {}
  const helper = (o1, o2, o3) => {
    let diff = true

    const keys = [...new Set([...Object.keys(o1), ...Object.keys(o2)])]
    keys.forEach(prop => {
      // 追加或删除的属性不需要做比较
      if (o1[prop] !== undefined && o2[prop] !== undefined) {
        const o1Child = o1[prop]
        const o2Child = o2[prop]

        // 1. 值类型和值类型比较
        if (isPrim(o1Child) && isPrim(o2Child)) {
          // 比较内容是否有差异
          if (o1Child !== o2Child) {
            o3[prop] = [o1Child, o2Child]
            diff = false
          }
        }
        // 2. 值类型和引用类型比较
        else if (isPrim(o1Child) || isPrim(o2Child)) {
          // 不同类型一定有差异
          o3[prop] = [o1Child, o2Child]
          diff = false
        }
        // 3. 引用类型和引用类型比较
        else {
          // 1) 不同引用类型的比较
          if (superTypeof(o1Child) !== superTypeof(o2Child)) {
            o3[prop] = [o1Child, o2Child]
            diff = false
          }
          // 2) 相同引用类型的比较
          else {
            const o3Child = {}
            if (!helper(o1Child, o2Child, o3Child)) {
              o3[prop] = o3Child
              diff = false
            }
          }
        }
      }
    })

    return diff
  }
  helper(obj1, obj2, res)

  return res
}

console.log(
  objDiff(
    {"a": {"b": [1, 2, 3]}},
    {"a": {"b": [1, 2, {}]}}
  )
)
